home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Freaks Macintosh Archive
/
Freaks Macintosh Archive.bin
/
Freaks Macintosh Archives
/
Textfiles
/
zines
/
DNA
/
DNAV1I9.sit
/
DNAV1I9
/
DNA109.012
< prev
next >
Wrap
Text File
|
1994-08-24
|
22KB
|
499 lines
_ _/ \_ _/ \_ _
_/ \_ / _ \__ _/ _ \_ _/ \_
_/ _ \_______/ \ | \_ | / \ _______/ _ \_
/ _/ \ \_ | |\_ \_ | | _/ / \_ \
\ / | |----\_ \_ | \_ \_ | |_/ _/----| | \ /
| | \_ \| \_ \_| / / | |
| | _/ || \|| \_ | |
| | _/ _/ | | \_ \_ | |
/ \_ | |__/ _/ | | | | \_ \__| | _/ \
\_ \_/ ______/\_/ | | \_/\_______ \_/ _/
\_ _/ \_ _/ \_ _/ \_ _/
\_/ \_/ \_/ \_/
VGA Programming
Although this subject is not one that normally accompanies a
typical P/H magazine, many people have asked questions about both
VGA and SVGA graphics programming, and I've decided to write up the
article as a response to all of them, instead of replying to each
individually. In this article, I'm not going to address SVGA
programming, since it's such a huge subject, and one that I need to
work on more. While this is far from a step by step tutorial in how
to infiltrate a specific system, I like to think that broad
knowledge of all aspects of programming are an integral part of
being a successful hacker. In that light, I hope you read and enjoy
what I have to present here. I have drawn this information from a
tremendous variety of sources. Many are books, and many are people
whose names are forgotten, but whose contributions are not.
What's Required of You
I assume you have some programming knowledge. If you don't,
this article will be virtually useless to you. All my sample code
is in either C or assembler. Most of it is pretty simple stuff, so
if you know either one even to some small amount, you'll be able to
handle this pretty easily. This is geared towards those who
understand programming well, and want to learn more about how to
take full control over thier systems. Understanding video cards
completely is a good start, and something that will take quite a
bit of time to get down. If you aren't a programmer, don't bother
reading on, you're just going to get bored to death learning about
VGA internals and the like. If you are, read on! You'll like what
you can do once you're done.
VGA architecture
Before I can go into detail on how to program a VGA card to do
what you want, I need to describe some of it's architecture. First
of all, standard VGA graphic's highest useful mode is 320 x 200 in
256 colors. While it is a bit crude looking, it is very useful for
plenty of applications. If you want to do better, you will need to
start dealing with SVGA graphics, which are far less standard than
VGA. I will go into SVGA graphics at another time. The subject is
just too damn big to put into this introductory text. Next, video
displays are what are called memory mapped devices. What does this
mean? Well, everyone knows that there is memory on your VGA card.
Typically, your standard SVGA card has 1 megabyte of VRAM on the
board. How do you access this memory on the card? The answer is
that an entire segment of memory(64k bytes) is reserved in your
first megabyte of system memory. Anything you write to this segment
of memory gets copied to the VGA card's memory directly. The base
memory is 'mapped' into the VRAM on the VGA card. Whatever is put
into the VRAM is what is displayed on the screen. It's simply a
matter of writing the right information in the right order.
Accessing the Memory Map
In standard VGA modes, you can't access more than the first
64k of RAM(out of the typical one meg) on the card. This is just a
limitation put in place by the current standards. DOS actually sets
aside two whole segments of memory for video display. However, one
is dedicated to text displays, and the other is for true graphics.
The size of the memory map for 320 by 200 is exactly 64000(320 *
200) bytes. The segment for graphics displays is located at 0A000h.
Writing to this segment will produce changes on your screen
assuming you are currently in graphics mode.
Understanding the Memory Map
Drawing single pixels on the screen is an extremely simple
task. Doing so requires you to write a single byte into the graphic
memory map at 0A000h. However, it's probably important to you
*where* on the screen that pixel is going to show up, so it's needs
to be discussed how the memory map relates to the pixels on the
screen. To do this, we need to consider the coordinate system for
IBM monitors. First of all, the starting corner of the screen is
the upper left. Think of that corner of the screen as (0,0). X
increases as you move further to the right. Y increases as you move
down. Obviously, in this graphics mode, you can have X values from
0 to 319, for a total of 320 pixels across the screen. Similarly,
you can have pixels from 0 to 199 for Y, totalling 200 pixels
vertically. As I've mentioned, the full address of the beginning of
the VGA screen is at 0xA000:0x0000. The '0x' before the numbers
denote the fact that they are in hexadecimal notation(base 16). To
move on to the next pixel (1,0), you would read from the offset
0x0001. Pixel (2,0) would be 0x0002, and so on. This is very easy,
until you hit the end of the line. The last pixel at the top of the
screen (319,0) is at offset 0x013F(319 decimal). The next line
starts at the very next byte in memory. So, the offset for (1,0) is
0x140(320 decimal). (1,1) is 0x0141. (1,2) is 0x0142 and so on.
Understanding how the memory is mapped on the screen is important,
since it's this map that you use to make useful images. If you
don't understand this, you've got to go back and read it again
until you do.
Imagine the asterisks below are the pixels of the screen, starting
at the upper left. The numbers to the left and right of the
'pixels' are the offsets of the first and last pixels on that line,
respectively.
VGA SCREEN:
0 |****************************************************| 319
320 |****************************************************| 639
640 |****************************************************| 959
960 |****************************************************|1279
1280|****************************************************|1599
Formula for Pixels
Now that you understand the memory mapping, how do you find
the offset for a given pixel on the screen? This is the toughest
part of writing to VGA screens. The algorithm itself is extremely
easy. The offset of an arbitrary pixel found with this algorithm:
Offset = (Y * 320) + X
This formula makes sense if you think about how the screen is laid
out. If you want to move down to line number Y, you have to add 320
to the offset to reach that point for every line you move down. If
you don't understand what I mean, just accept that the formula is
correct, and figure it out for yourself later. Now that you know
the formula, you can write a generic function or macro that does
this in either C, assembly, or some other language. Personally,
considering how simple an algorithm this is, I'd go ahead and code
it in assembly. Besides, it's handy to stay away from the
multiplication functions that high level languages use. Here is
some very quick code to determine offsets given an X and a Y value:
MOV AX,Y
MOV BX,X
MOV CL,6
SHL AX,CL ; Fast multiply of Y times 64
ADD BX,AX ; Add Y to X
ADD AX,AX ; Multiply Y times 2 again
ADD AX,AX ; Multiply Y times 2 again
ADD BX,AX ; Add total result into BX
If you have no way of using assembly, by all means use similar code
in whatever language you wish. For example, a function in C would
look like this:
unsigned char far *screen;
screen = MK_FP(0xA000,(y * 320) + x);
Here's a tiny C program to calculate offsets for you, in both
Decimal and Hex:
#include <stdio.h>
#include <dos.h>
void main(void)
{
int x,y;
unsigned int offset;
printf("Input X and Y coordinates for VGA 13h mode offset:\n");
scanf("%d",&x);
scanf("%d",&y);
offset = (y * 320) + x;
printf("%d, 0x%x\n",offset,offset);
}
Most of my example code in this article will be in C, since with
this graphic mode, it's not very critical that it be optimized to
death. When dealing with SVGA graphics, writing efficient code is
nearly impossible in C. But we're not there yet, and we should be
thankful for that.
Accessing Memory
Now that we have our memory address stored into a far pointer,
how do we use that information? In fact, it's extremely easy. You
will only need to deal with offsets once the '*screen' pointer has
been setup. You will never need to change the value of *screen,
since you can now access screen as an array of characters. To write
a pixel of arbitrary value '87' to the location (0,0) on the
monitor:
unsigned char far *screen;
screen = MK_FP(0xA000,0);
screen[0] = 87;
That's all there is to it! Once you're in graphics mode, you simply
choose the offset of the pixel you want to write, and put that
offset into the screen array to be dereferenced. We can use the
above formula we learned to make a general function to write a
pixel to any spot on the screen:
int plotpoint(int x, int y, char color)
{
if( (x > 319) || (x < 0) || (y > 199) || (y < 0) )
return 0;
screen[(y * 320) + x] = color;
return 1;
}
This code does assume that the '*screen' pointer is a global
variable. I'm sure we all know that using global variables is 'bad'
by most standards. Well, damn it, the screen object is something
that lots of functions need access to, and there's only one screen
anyway, right? Sending another 32 bit pointer on the stack every
time you want to use a subroutine that accesses the screen is
wasting time and space. This routine returns a 1 if it's
successful, and 0 if the X or Y coordinates were out of bounds.
Reading a pixel is just as easy as setting it. You use the
same global *screen pointer to access it, but read from the memory
location rather than writing to it. Here it is:
int readpoint(int x, int y, char *color)
{
if( (x > 319) || (x < 0) || (y > 199) || (y < 0) )
return 0;
color = screen[(y * 320) + x];
return 1;
}
This subroutine returns the same values as the one above, and
it also returns the value of the color. Remember you have to send
the function the pointer to your color variable when calling this
routine, thusly:
unsigned char pixel;
readpoint(45,56,&pixel);
All this is pretty straight forward coding. C is very flexible
when it comes to arranging memory. The most difficult part is
managing all the memory tasks by yourself. Don't be surprised when
you get lots of null pointer assignments and the like. Working with
memory and pointers is extremely taxing and tedious. Getting it
right is tough, but rewarding in the end.
As it turns out, dereferencing a far pointer every time you
want to write or write a pixel on the screen isn't terribly
efficient. If you are going to read or write in a row, it is a much
better idea to optimize that memory access at the same time. You
can do it with C, or write yourself a very simple, and highly
optimized assembly routine to do the same thing. Let's say you have
a buffer(array of chars) which you want to write directly to the
screen. How can you move that to the position you want on the
screen directly without dereferencing far pointers a lot? Here it
is in assembly:
g_memwrite proc uses ES SI DI, inbuf:WORD, start:WORD, length:WORD
MOV CX,length ; How many bytes to write
MOV AX,0A000h ; Video Memory segment
MOV SI,inbuf ; offset of byte array to be written
MOV ES,AX ; Set ES to video memory segment
MOV DI,start ; Where on screen to start(offset)
REP MOVSB ; Write CX bytes to screen
RET
g_memwrite endp
Here are some useful functions in assembly for working with
mode 13h VGA. For setting the VGA screen to graphics mode:
void set_vgamode(void);
set_vgamode PROC
MOV AX,13h
INT 10h
RET
set_vgamode ENDP
For setting back to 80x25 text mode:
void set_textmode(void);
set_textmode PROC
MOV AX,3
INT 10h
RET
set_textmode ENDP
For clearing the whole screen to a given color:
void clearscrn(char color);
clearscrn PROC USES ES DI, color:byte
MOV AX,0A000h
MOV ES,AX ;Point ES:DI to
XOR DI,DI ; the screen map
MOV CX,64000 ;Set repeat to 64000
MOV AL, color ;Color to be written to all pixels
REP STOSB ;Write all pixels at once
RET
clearscrn ENDP
For writing a pixel to the screen at (X,Y) with a given color.
Requires a 286 to execute this code, since it uses immediates for
the shifts:
void plotpoint(int x, int y, char color);
plotpoint proc USES ES, X:word, Y:word, color:byte
MOV AX,0A000h
MOV ES,AX
MOV AX,Y
MOV BX,X
SHL AX,6
ADD BX,AX
SHL AX,2
ADD BX,AX
MOV AL, byte ptr color
MOV ES:[BX], AL
RET
plotpoint ENDP
Now that we've gotten into how to write directly to the video
screen, let's look at another important subject in video
programming. The VGA palette may be the most difficult aspect of
VGA programming to grasp.
VGA Palette
Now that we understand how to write certain values into video
memory, it's time to discuss how those values are translated into
color. A palette is how you describe what colors are going to be
shown on the screen for a given value. Let's say you write the
value '68' to some pixel on your screen. How does your VGA card
decide what color is associated with that number 68? How do you set
your card to display a given color? The VGA palette for each of the
256 colors is a 3 byte series. The first byte is the value for Red,
the second for Green, and the third for Blue. Each of those bytes
can only use the bottom 6 bits, meaning that their values can be
between 0 and 63 only. Being at a value of 0 means that the color
is completely turned off. The color black is achieved by setting
all three bytes to 0. Setting a color to 63 turns it full on.
Setting all three bytes to 63 will be the color white. Everything
else inbetween can be achieved with different combinations. There
are a total of 262,144 colors that can be displayed on a VGA card,
256 of which can be displayed at once. Since there are 256 colors
in the VGA palette, the palette in memory takes up 768 bytes(256 *
3). If you are going to manipulate the palette, you're going to
want to keep it in memory, since reading from the card is slow and
difficult.
Manipulating the Palette
Writing the values you want to the VGA card is actually quite
simple. While there are other considerations when you're doing
this, all you have to do is write three bytes to the correct IO
port. First, to write to the palette for color number 68 for
example, you have to write the number 68 to port 0x3C8. After that,
you have to write the three byte values in order to port number
0x3C9. If you don't understand this, look at the pseudocode
example:
write 68 to port 0x3C8
write Red byte to port 0x3C9
write Green byte to port 0x3C9
write Blue byte to port 0x3C9
It's quite simple, as you can see. The only difficult part of this
is setting the values of the palette to values you want. There is
one level of complication that needs to be added to this simple
model though. On some systems, this will cause lots of static, or
snow on the screen whenever a palette register is set. This is
caused for some esoteric reason on crappy VGA cards. The way around
this is to only write to the screen while the monitor is retracing.
If you don't know how a monitor paints the screen, it moves an
electron beam along each of the pixel lines, and when it gets to
the bottom, it has to move the beam back up to the upper left of
the screen to start drawing again. While it's moving back up to the
top of the screen, obviously nothing is being written to the
monitor. This is good for us, since no static shows up at this time
either. All we have to do is make sure we're in a retrace state,
and then set our palette registers. Checking retrace requires that
you read port 0x3DA. Reading a port means that you get a byte of
data in return. Only the third bit of data is useful to us right
now. If that bit is a one, then the screen is in a retrace
currently, and anything written to it will have little or no snow.
We need to make sure that we don't accidentally come by the retrace
at the very last second, and write a whole bunch of data to the VGA
card even after it's done with the retrace. To stop that, we will
have two loops that wait for the retrace. That being the case, lets
examine the new p-code.
non_retrace:
read port 0x3DA
if bit 3 = 1 goto non_retrace
retrace:
read port 0x3DA
if bit 3 = 0 goto retrace
write 68 to port 0x3C8
write Red byte to port 0x3C9
write Green byte to port 0x3C9
write Blue byte to port 0x3C9
Typically, about 128 of the 256 color palette can be written in a
single retrace, so loop the last statement for 128 times:
non_retrace:
read port 0x3DA
if bit 3 = 1 goto non_retrace
retrace:
read port 0x3DA
if bit 3 = 0 goto retrace
for x=0 to 127
{
write x to port 0x3C8
write Red byte for color x to port 0x3C9
write Green byte for color x to port 0x3C9
write Blue byte for color x to port 0x3C9
}
Rewriting this in another language shouldn't take too long. Here is
the code to do just this in C:
set_vgapalette(char *p) //p is the 768 byte palette array
{
unsigned int i,j,end;
for(i=1; i<=2; i++) //Do twice
{
end = 128 * i;
while( 1 == inp(0x3DA) & 0x08 ); //Loop while bit 3 = 1(retrace)
while( 0 == inp(0x3DA) & 0x08 ); //Loop while bit 3 = 0(not)
for(j=128 * (i - 1); j<end; j++) //Write first or second 128
{ // colors to palette registers
outp(0x3C8,j); //Color to be written next
outp(0x3C9,p++); //Red byte of palette color
outp(0x3C9,p++); //Green byte..
outp(0x3C9,p++); //Blue byte..
}
}
}
This is a pretty quick little routine for setting the palette. Of
course, this sets the entire palette every time you call it. If you
only are changing a small part of the palette, it'll be faster to
write a function that sets only that portion you changed. Another
option is to rewrite this in optimized assembly code. Personally,
I think all your VGA primitives should be written in assembly for
the sake of speed and size. If you don't know assembly though,
you're pretty much out of luck. All I can do is give you some
standard routines to use for yourself, and recommend you try
learning it. It's a big help to you when writing in every language.
For instance, many C or Pascal compilers have an option of
compiling your C code to assembly. Being able to see what the
compiler does with your code can help you optimize even straight C
routines a lot. For instance, in the code above, I put made the
variable 'end' because I looked at how C assembles the FOR loop,
and realized that it calculates the second part of the FOR
statement every time it loops. Instead of multiplying I*128 every
time the loop comes by, it just compares it with the memory
location 'end'. It's much faster, and makes the loop quicker, so
there's no snow on the screen. Enough crap on the wrong subject.
Let's get back to palette functions. Since you can change the
palette while you've already got something displayed on the screen,
interesting effects can be produced. Let's say you want to fade
your screen at the end of a sequence to black. The value black is
achieved by setting all three palette colors to zero. So, if we
want to fade the entire palette to black over time, we just have to
subtract one from each of the 768 byte colors from the palette in
memory. Look at the code, not at my writing:
for(i=0; i<768; i++)
{
if palette[i] > 0 then palette[i] = palette[i] - 1;
}
set_vgapalette(palette);
This code does just what I mentioned. It subtracts one from each
byte color, and once it's done all 768 of them, it displays the new
palette using the set_vgapalette function. Many other interesting
functions can be achieved using palette changing routines such as
this. You could fade to white, slowly swap from one palette to
another, rotate palettes to create a moving effect. For instance,
you could make an fiery explosion seem to move by changing the
palette from white to yellow to red slowly. The particular
application is up to you; use your imagination.
That's about all there is too it. There are lots of other
graphics modes you could use, and lots of other things that could
be mentioned. However, only so much can be taught to you. Much of
the understanding necessary for writing good graphics code must
simply be learned by trial and error coming from hands on
programming. While I don't fool myself into believing many people
will benefit from this introduction, I hope someone enjoyed it. I
have spent many, many hours learning VGA and SVGA programming, and
I hope this little beginning will be enough for at least a few
individuals to start off on their own. If you read this and have
questions, or would like to see another article written on a more
specific subject, please do mail me on Digital Decay, or some board
that picks up DNAnet. I'll probably do an article on SVGA cards at
a later time, but I need more time to work on that. Until such
time, pleasant programming and happy hacking.
Zephyr [Cosys - Digital Decay] 8/94